package cn.zf233.xcloud.util;

import cn.zf233.xcloud.commom.Const;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by zf233 on 2021/1/22
 * 二维码工具类
 */
@Component
public class QRCodeUtil {

    // 编码
    private static final String CHARSET = "UTF-8";
    // 文件格式
    private static final String FORMAT = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 300;
    // LOGO宽度
    private static final int LOGO_WIDTH = 65;
    // LOGO高度
    private static final int LOGO_HEIGHT = 65;

    public String decodeQRCodeForPath(String path) {
        // 该文件对象映射二维码图片
        File file = new File(path);
        if (file.exists()) {
            try {
                return decodeQRCodeStreamForStream(new FileInputStream(file));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public void create(String content, String logo, OutputStream output) throws Exception {
        BufferedImage image = createImage(content, logo, null, LogoTypeENUM.CLASS_PATH, true);
        ImageIO.write(image, FORMAT, output);
        output.close();
    }

    public void create(String content, OutputStream output) throws Exception {
        BufferedImage image = createImage(content, null, null, LogoTypeENUM.NONE, true);
        ImageIO.write(image, FORMAT, output);
        output.close();
    }

    public String createAndUpload(String content, String logo, String uploadFileName, Upload upload) throws Exception {
        BufferedImage image = createImage(content, logo, null, LogoTypeENUM.REAL_PATH, true);
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ImageOutputStream imOut = ImageIO.createImageOutputStream(bs);
        ImageIO.write(image, "jpg", imOut);
        InputStream is = new ByteArrayInputStream(bs.toByteArray());
        return upload.upload(uploadFileName, is);
    }

    public String createAndUpload(String content, String uploadFileName, Upload upload) throws Exception {
        BufferedImage image = createImage(content, null, null, LogoTypeENUM.NONE, true);
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ImageOutputStream imOut = ImageIO.createImageOutputStream(bs);
        ImageIO.write(image, "jpg", imOut);
        InputStream is = new ByteArrayInputStream(bs.toByteArray());
        return upload.upload(uploadFileName, is);
    }

    public synchronized String decodeQRCodeStreamForStream(InputStream input) {
        BufferedImage image;
        if (input != null) {
            try {
                image = ImageIO.read(input);
                LuminanceSource source = new BufferedImageLuminanceSource(image);
                BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

                Map<DecodeHintType, Object> hints = new LinkedHashMap<>();
                hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
                hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
                hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
                Result result = new MultiFormatReader().decode(bitmap, hints);
                return result.getText();
            } catch (IOException | NotFoundException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public synchronized BufferedImage createImage(String content, String logo, InputStream logoStream, LogoTypeENUM logoType, boolean compress) throws Exception {
        Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter()
                .encode(
                        content,
                        BarcodeFormat.QR_CODE,
                        QRCODE_SIZE,
                        QRCODE_SIZE,
                        hints
                );
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        switch (logoType) {
            case STREAM:
                break;
            case REAL_PATH:
                logoStream = new FileInputStream(logo);
                break;
            case CLASS_PATH:
                Resource resource = new ClassPathResource(Const.SHARE_QR_LOGO_CLASS_PATH);
                logoStream = resource.getInputStream();
                break;
            case URL:
                URL url = new URL(logo);
                URLConnection connection = url.openConnection();
                connection.connect();
                logoStream = connection.getInputStream();
                break;
            default:
                return image;
        }
        insertImage(image, logoStream, compress);
        return image;
    }

    private void insertImage(BufferedImage source, InputStream logoStream, boolean compress) throws IOException {
        try {
            Image src = ImageIO.read(logoStream);
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            if (compress) {
                width = Math.min(width, LOGO_WIDTH);
                height = Math.min(height, LOGO_HEIGHT);
                Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
                BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics g = tag.getGraphics();
                g.drawImage(image, 0, 0, null);
                g.dispose();
                src = image;
            }
            Graphics2D graph = source.createGraphics();
            int x = (QRCODE_SIZE - width) / 2;
            int y = (QRCODE_SIZE - height) / 2;
            graph.drawImage(src, x, y, width, height, null);
            Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
            graph.setStroke(new BasicStroke(3f));
            graph.draw(shape);
            graph.dispose();
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (logoStream != null) {
                try {
                    logoStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public interface Upload {
        String upload(String fileName, InputStream inputStream) throws IOException;
    }

    public enum LogoTypeENUM {

        STREAM(0, "logoStream"),
        REAL_PATH(1, "logoRealPath"),
        CLASS_PATH(2, "logoClassPath"),
        URL(3, "logoClassPath"),
        NONE(4, "noneLogo");

        private final Integer code;
        private final String describe;

        LogoTypeENUM(Integer code, String describe) {
            this.code = code;
            this.describe = describe;
        }

        public Integer getCode() {
            return code;
        }

        public String getDescribe() {
            return describe;
        }
    }
}